#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <ctype.h>

#include "func.h"
#include "cJSON.h"
#include "cpu.h"
#include "private_common.h"

#define NUM_CPUSTATES_4X 4
#define NUM_CPUSTATES_8X 7

enum CORE_STATS_INDEX_MAX{
	INDEX_USER=0,
	INDEX_NICE,
	INDEX_SYSTEM,
	INDEX_IDLE,
	INDEX_IOWAIT,
	INDEX_IRQ,
	INDEX_SOFTIRQ,
	INDEX_STEAL,
	INDEX_GUEST,
	INDEX_GUEST_NICE,
	CORE_STATS_INDEX_MAX
};


typedef struct {
	uint64_t ctxt;
	uint64_t btime;
	uint32_t processes;
	uint32_t procs_running;
	uint32_t procs_blocked;
} CPUStats, *PCPUStats;

char stat_buffer[BUFFSIZE];
timely_file cpu_stat    = { {0,0} , 0, "/proc/stat", stat_buffer, BUFFSIZE };

static char* cpuinfo_freq_suffix[3] = {
	"cpuinfo_min_freq",
	"cpuinfo_max_freq",
	"cpuinfo_cur_freq"
};

struct cpu_data
{
	uint16_t  core;
	uint64_t freq_min;
	uint64_t freq_max;
	uint64_t freq_cur;
	// jiffies
	//uint64_t value_jiffies[CORE_STATS_INDEX_MAX];
	//uint64_t value_prev[CORE_STATS_INDEX_MAX];
	struct {
		uint64_t user;
		uint64_t nice;
		uint64_t system;
		uint64_t idle;
		uint64_t iowait;
		uint64_t irq;
		uint64_t softirq;
		uint64_t steal;
		uint64_t guest;
		uint64_t guest_nice;
	}value_jiffies, value_prev;
	uint64_t total_jiffies;
	double   rate;
};

static struct cpu_data* cpudata = NULL;
static uint16_t cpu_cores = 0;
static CPUStats cpustats;

static int parse_core(char* st)
{
	int core_index;
	int k;
	char *p;
	p = st;
	p += 3;
	if (!p) {
		return -1;
	}
	if (isdigit(*p)) {
		core_index = strtod(p, &p);
		if (core_index < 0 || core_index >= cpu_cores) {
			return -2;
		}
	} else if (isspace(*p)) {
		core_index = cpu_cores;
	}
	struct cpu_data* cd = (cpudata+core_index);
	cd->value_prev.user       = cd->value_jiffies.user;
	cd->value_prev.nice       = cd->value_jiffies.nice;
	cd->value_prev.system     = cd->value_jiffies.system;
	cd->value_prev.idle       = cd->value_jiffies.idle;
	cd->value_prev.iowait     = cd->value_jiffies.iowait;
	cd->value_prev.irq        = cd->value_jiffies.irq;
	cd->value_prev.softirq    = cd->value_jiffies.softirq;
	cd->value_prev.steal      = cd->value_jiffies.steal;
	cd->value_prev.guest      = cd->value_jiffies.guest;
	cd->value_prev.guest_nice = cd->value_jiffies.guest_nice;
	
	cd->value_jiffies.user       = strtoul(p, &p, 10);
	cd->value_jiffies.nice       = strtoul(p, &p, 10); 
	cd->value_jiffies.system     = strtoul(p, &p, 10); 
	cd->value_jiffies.idle       = strtoul(p, &p, 10); 
	cd->value_jiffies.iowait     = strtoul(p, &p, 10); 
	cd->value_jiffies.irq        = strtoul(p, &p, 10); 
	cd->value_jiffies.softirq    = strtoul(p, &p, 10); 
	cd->value_jiffies.steal      = strtoul(p, &p, 10); 
	cd->value_jiffies.guest      = strtoul(p, &p, 10); 
	cd->value_jiffies.guest_nice = strtoul(p, &p, 10); 

	//printf("%ld %ld %ld %ld\n", cd->value_prev.user, cd->value_prev.system, cd->value_prev.idle, cd->value_prev.iowait);
	//printf("%ld %ld %ld %ld\n", cd->value_jiffies.user, cd->value_jiffies.system, cd->value_jiffies.idle, cd->value_jiffies.iowait);
}

static void parse_cpu_stat(void* ud, void* up, char* st)
{
	const char delim[] = "\n";
	char *token;
	char *p;
	token = strtok(st, delim);
	int k = 0;
	while (token !=NULL) {
		if (startsWith(token, "cpu")) {
			parse_core(token);
		} else if (startsWith(token, "ctxt")) {
			p = token;
			p = skip_token(p);
			p = skip_whitespace(p);
			cpustats.ctxt = strtod(p, &p);
		} else if (startsWith(token, "btime")) {
			p = token;
			p = skip_token(p);
			p = skip_whitespace(p);
			cpustats.btime = strtod(p, &p);
		} else if (startsWith(token, "processes")) {
			p = token;
			p = skip_token(p);
			p = skip_whitespace(p);
			cpustats.processes = strtod(p, &p);
		} else if (startsWith(token, "procs_running")) {
			p = token;
			p = skip_token(p);
			p = skip_whitespace(p);
			cpustats.procs_running = strtod(p, &p);
		} else if (startsWith(token, "procs_blocked")) {
			p = token;
			p = skip_token(p);
			p = skip_whitespace(p);
			cpustats.procs_blocked = strtod(p, &p);
		} else {
			//printf("line%d=%s\n", ++k, token);
		}
		token = strtok(NULL, delim);
	}
	uint64_t total_diff, total_prev;
	uint64_t idle_diff, idle_prev, idle_cur;
	for (k = 0;k < cpu_cores+1; k++) {
		total_prev = (cpudata+k)->total_jiffies;
		idle_prev = (cpudata+k)->value_prev.idle;
		idle_cur  = (cpudata+k)->value_jiffies.idle;
		(cpudata+k)->total_jiffies = 0;
		(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.user;
		(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.nice;
		(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.system;
		(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.idle;
		if (cpu_cores == NUM_CPUSTATES_4X) {
		} else if (cpu_cores == NUM_CPUSTATES_8X) {
			idle_prev += (cpudata+k)->value_prev.iowait;
			idle_cur  += (cpudata+k)->value_jiffies.iowait;
			(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.iowait;
			(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.irq;
			(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.softirq;
		} else {
			(cpudata+k)->total_jiffies += (cpudata+k)->value_jiffies.steal;
		}
		total_diff = (cpudata+k)->total_jiffies-total_prev;
		idle_diff  = idle_cur-idle_prev;
		(cpudata+k)->rate = total_diff > 0 ? 100.0*(total_diff - idle_diff) / total_diff : 0;
		(cpudata+k)->rate = ((int)((cpudata+k)->rate)*10.0/10.0);
	}
}

static void parse_cpu_freq(void* ud, void* up, char* st) {
	struct cpu_data* cpudata = (struct cpu_data*)ud;
	int* index = (int*)up;
	if (ud == NULL || index == NULL) return;
	if (st == NULL) return;
	char* p = st;
	if (*index == 0)
		cpudata->freq_min = strtod(p, &p);
	else if (*index == 1)
		cpudata->freq_max = strtod(p, &p);
	else if (*index == 2)
		cpudata->freq_cur = strtod(p, &p);
	else {
		//err
	}
}

static int  cpu_init(p_monitor_node *ml, const char* ignore) 
{
	char line[1024];
	char *p;
	char path[512];
	FILE* cpus = fopen("/proc/stat","r");
	if (!cpus) {
		return -1;
	}
	
	cpu_cores = 0;
	while (fgets(line, sizeof(line), cpus) ) {
		p = skip_whitespace(line);
		if (startsWith(p, "cpu")) {
			p += 3;
			if (isdigit(*p)) {
				cpu_cores++;
			}
		}
	}
	fclose(cpus);
	
	size_t  s = sizeof(monitor_node) * (cpu_cores * 3 + 1);
	*ml = (p_monitor_node)malloc(s);
	cpudata = (struct cpu_data*)malloc(sizeof(struct cpu_data)*(cpu_cores+1));
	memset(cpudata, 0, sizeof(struct cpu_data)*(cpu_cores+1));

	static int type[3] = {0, 1, 2};
	for (int i = 0;i < cpu_cores; i++) {
		for (int j = 0; j < 3; j++) {
			sprintf(path, "/sys/devices/system/cpu/cpu%d/cpufreq/%s", i, cpuinfo_freq_suffix[type[j]]);
			(cpudata+i)->core = i;
			init_monitor_info_interface(&((*ml+i*3+j)->oper), parse_cpu_freq, (void*)(cpudata+i), (void*)(&(type[j])));
			init_monitor_node(*ml+i*3+j, path, 1, 0);
		}
	}

	init_monitor_info_interface(&(((*ml+cpu_cores*3))->oper), parse_cpu_stat, NULL, NULL);
	init_monitor_node((*ml+cpu_cores*3), "/proc/stat", 0.01, 0);
	return (cpu_cores * 3 + 1);
}

static void cpu_update(p_monitor_node *ml)
{
	int num = cpu_cores * 3 + 1;
	for (int i = 0;i < num; i++) {
		update_monitor_node(*ml+i);
	}
	usleep(50000);
	update_monitor_node(*ml+num-1);

}

static void cpu_to_json(cJSON* json)
{
	char tmp[32];
	cJSON *item, *node, *array;
	int k;
	for (int i = 0;i < cpu_cores+1; i++) {
		if (i == cpu_cores) {
			snprintf(tmp, 31, "cpu");
		} else {
			snprintf(tmp, 31, "cpu%d", i);
		}
		item = cJSON_GetObjectItemCaseSensitive(json, tmp);
		if (item) {
			node = cJSON_GetObjectItem(json, tmp);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "cpuinfo_min_freq"), (cpudata+i)->freq_min);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "cpuinfo_max_freq"), (cpudata+i)->freq_max);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "cpuinfo_cur_freq"), (cpudata+i)->freq_cur);

			array = cJSON_GetObjectItem(node, "jiffies");
			cJSON_SetNumberValue(cJSON_GetArrayItem(array, 0), (cpudata+i)->value_jiffies.user);
			cJSON_SetNumberValue(cJSON_GetArrayItem(array, 1), (cpudata+i)->value_jiffies.system);
			cJSON_SetNumberValue(cJSON_GetArrayItem(array, 2), (cpudata+i)->value_jiffies.idle);
			cJSON_SetNumberValue(cJSON_GetArrayItem(array, 3), (cpudata+i)->value_jiffies.iowait);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "rate"), (cpudata+i)->rate);
		} else {
			node = cJSON_CreateObject();
			cJSON_AddNumberToObject(node, "cpuinfo_min_freq", (cpudata+i)->freq_min);
			cJSON_AddNumberToObject(node, "cpuinfo_max_freq", (cpudata+i)->freq_max);
			cJSON_AddNumberToObject(node, "cpuinfo_cur_freq", (cpudata+i)->freq_cur);
			cJSON_AddItemToObject(json, tmp, node);

			array = cJSON_CreateArray();
			cJSON_AddItemToArray(array, cJSON_CreateNumber((cpudata+i)->value_jiffies.user));
			cJSON_AddItemToArray(array, cJSON_CreateNumber((cpudata+i)->value_jiffies.system));
			cJSON_AddItemToArray(array, cJSON_CreateNumber((cpudata+i)->value_jiffies.idle));
			cJSON_AddItemToArray(array, cJSON_CreateNumber((cpudata+i)->value_jiffies.iowait));
			cJSON_AddItemToObject(node, "jiffies", array);
			cJSON_AddNumberToObject(node, "rate", (cpudata+i)->rate);
		}
	}
}

static uint32_t cpu_to_text(char* buffer, uint32_t size)
{
	char tmp[16];
	char line[4][64];
	uint32_t k1 = 0, k2 = size;
	int r;
	for (int i = 0;i < cpu_cores+1; i++) {
		if (i == cpu_cores) {
			snprintf(tmp, 31, "cpu");
		} else {
			snprintf(tmp, 31, "cpu%d", i);
		}
		sprintf(line[0], "%s,cpuinfo_min_freq,%lld", tmp, (cpudata+i)->freq_min);
		sprintf(line[1], "%s,cpuinfo_max_freq,%lld", tmp, (cpudata+i)->freq_max);
		sprintf(line[2], "%s,cpuinfo_cur_freq,%lld", tmp, (cpudata+i)->freq_cur);
		sprintf(line[3], "%s,rate,%.0f", tmp, (cpudata+i)->rate);
		r = snprintf(buffer+k1, k2, "%s\n%s\n%s\n%s\n", line[0], line[1], line[2], line[3]);
		if (r <= 0) return k1;
		k1 += r;
		if (k1 >= size) break;
		k2 -= r;
	}
	return k1;
}

static void cpu_clean()
{
	if (cpudata)
		free(cpudata);
	cpudata = NULL;
}

void cpu_task_init(ptask t, char* _ignore, uint16_t _uf)
{
        t->init    = cpu_init;
        t->update  = cpu_update;
        t->to_json = cpu_to_json;
        t->clean   = cpu_clean;
        t->json    = NULL;
        t->ignore  = _ignore;
        t->uf      = _uf;
        t->cu      = 0;
	t->to_text = cpu_to_text;
}

